package thesis.filesharing.dao.impl;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.stereotype.Repository;

import thesis.filesharing.common.exception.functional.ObjectNotFoundException;
import thesis.filesharing.common.exception.system.AppSystemException;
import thesis.filesharing.common.valueobjects.SearchClassValueObject;
import thesis.filesharing.dao.ClassDAO;
import thesis.filesharing.dao.util.EMF;
import thesis.filesharing.model.Clazz;
import thesis.filesharing.model.Lecturer;
import thesis.filesharing.model.Student;
import thesis.filesharing.model.Subject;

import com.google.appengine.api.datastore.Key;

@Repository
public class ClassDAOImpl implements ClassDAO {

	@SuppressWarnings("unchecked")
	@Override
	public List<Clazz> listClasses(int page, int pageSize) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT c FROM Clazz c";
			Query query = em.createQuery(jpaql);
			query.setFirstResult((page - 1) * pageSize);
			query.setMaxResults(pageSize);
			return (List<Clazz>) query.getResultList();
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public Clazz findClassByIdAndSemester(String classId, Integer semester) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT c FROM Clazz c WHERE c.classId = :classId AND c.semester = :semester";
			Query query = em.createQuery(jpaql);
			query.setParameter("classId", classId);
			query.setParameter("semester", semester);
			List<Clazz> classes = (List<Clazz>) query.getResultList();
			if (classes != null && !classes.isEmpty()) {
				return classes.get(0);
			}
			return null;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@Override
	public Clazz findClassByKey(Key classKey) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			return em.find(Clazz.class, classKey);
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@Override
	public Clazz saveClass(Clazz clazz, Key subjectKey) {
		EntityManager em = null;
		EntityTransaction transaction = null;
		try {
			em = EMF.get().createEntityManager();
			transaction = em.getTransaction();
			transaction.begin();
			
			Subject s = em.find(Subject.class, subjectKey);
			if (s != null) {
				s.getClasses().add(clazz);
			}
			
			em.merge(s);
			em.flush();
			
			transaction.commit();
			return clazz;
		} catch (Exception ex) {
			if (transaction != null && transaction.isActive()) {
				transaction.rollback();
			}
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public Clazz updateClass(Clazz clazz, String subjectId) {
		EntityManager em = null;
		EntityTransaction transaction = null;
		
		try {
			em = EMF.get().createEntityManager();
			transaction = em.getTransaction();
			transaction.begin();
			
			Clazz c = em.find(Clazz.class, clazz.getClassKey());
			if (c != null) {
				if (subjectId.equals(c.getSubject().getSubjectId())) {
					c.setClassId(clazz.getClassId());
					c.setClassName(clazz.getClassName());
					c.setSemester(clazz.getSemester());
					c.setLecturer(clazz.getLecturer());
					em.merge(c);
				} else {
					String jpaql = "SELECT s FROM Subject s WHERE s.subjectId = :subjectId";
					Query query = em.createQuery(jpaql);
					query.setParameter("subjectId", subjectId);
					List<Subject> subjects = (List<Subject>) query.getResultList();
					if (subjects != null && !subjects.isEmpty()) {
						Subject s = subjects.get(0);
						// Add new entity
						Clazz c1 = new Clazz(clazz.getClassId(), clazz.getClassName(), clazz.getSemester());
						c1.setLecturer(clazz.getLecturer());
						s.getClasses().add(c1);
						// Delete old entity
						em.remove(c);
						em.merge(s);
					}
				}
				em.flush();
			}
			
			transaction.commit();
			return c;
		} catch (Exception ex) {
			if (transaction != null && transaction.isActive()) {
				transaction.rollback();
			}
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@Override
	public void deleteClass(Key classKey) {
		EntityManager em = null;
		EntityTransaction transaction = null;
		try {
			em = EMF.get().createEntityManager();
			transaction = em.getTransaction();
			transaction.begin();
			Clazz c = em.find(Clazz.class, classKey);
			if (c != null) {
				em.remove(c);
				em.flush();
			}
			transaction.commit();
		} catch (Exception ex) {
			if (transaction != null && transaction.isActive()) {
				transaction.rollback();
			}
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@Override
	public int countClasses() {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT c FROM Clazz c";
			Query query = em.createQuery(jpaql);
			return query.getResultList().size();
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean checkClassIdExistedInSemester(String classId, Integer semester) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			String jpaql = "SELECT c FROM Clazz c WHERE c.classId = :classId AND c.semester = :semester";
			Query query = em.createQuery(jpaql);
			query.setParameter("classId", classId);
			query.setParameter("semester", semester);
			List<Clazz> classes = (List<Clazz>) query.getResultList();
			return classes != null && !classes.isEmpty();
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Clazz> searchClasses(SearchClassValueObject searchObj, int page, int pageSize) {
		Query query = getSearchQuery(searchObj);
		if (query == null) {
			return new ArrayList<Clazz>();
		} else {
			List<Clazz> list = (List<Clazz>) query.getResultList();
			int from = pageSize * (page - 1);
			int to = from + pageSize > list.size() ? list.size() : from + pageSize;
			return list.subList(from, to);
//			return (List<Clazz>) query.setFirstResult((page - 1) * pageSize)
//					.setMaxResults(pageSize).getResultList();
		}
	}

	@Override
	public int countFoundClasses(SearchClassValueObject searchObj) {
		Query query = getSearchQuery(searchObj);
		return query == null ? 0 : query.getResultList().size();
	}
	
	@SuppressWarnings("unchecked")
	private Query getSearchQuery(SearchClassValueObject searchObj) {
		EntityManager em = null;
		try {
			em = EMF.get().createEntityManager();
			CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
			CriteriaQuery<Clazz> criteriaQuery = criteriaBuilder.createQuery(Clazz.class);
			Root<Clazz> root = criteriaQuery.from(Clazz.class);
			List<Predicate> predicates = new ArrayList<Predicate>();
			
			if (searchObj.getClassId() != null && searchObj.getClassId().length() > 0) {
				Expression<String> classIdExpression = root.get("classId");
				predicates.add(criteriaBuilder.equal(classIdExpression, searchObj.getClassId()));
			}
			
			if (searchObj.getSubjectId() != null && searchObj.getSubjectId().length() > 0) {
				String jpaql = "SELECT s FROM Subject s WHERE s.subjectId = :subjectId";
				Query query = em.createQuery(jpaql);
				query.setParameter("subjectId", searchObj.getSubjectId());
				List<Subject> subjects = (List<Subject>) query.getResultList();
				if (subjects == null || subjects.isEmpty()) {
					return null;
				} else {
					Expression<Subject> subjectExpresison = root.get("subject");
					predicates.add(criteriaBuilder.equal(subjectExpresison, subjects.get(0)));
				}
			}
			
			if (searchObj.getLecturerId() != null && searchObj.getLecturerId().length() > 0) {
				String jpaql = "SELECT l FROM Lecturer l WHERE l.lecturerId = :lecturerId";
				Query query = em.createQuery(jpaql);
				query.setParameter("lecturerId", searchObj.getLecturerId());
				List<Lecturer> lecturers = (List<Lecturer>) query.getResultList();
				if (lecturers == null || lecturers.isEmpty()) {
					return null;
				} else {
					Expression<Lecturer> lecturerExpression = root.get("lecturer");
					predicates.add(criteriaBuilder.equal(lecturerExpression, lecturers.get(0)));
				}
			}
			
			if (searchObj.getSemester() != null) {
				Expression<Integer> semesterExpression = root.get("semester");
				predicates.add(criteriaBuilder.equal(semesterExpression, searchObj.getSemester()));
			}
			
			if (predicates.isEmpty()) {
				criteriaQuery.select(root);
			} else {
				criteriaQuery.select(root).where(predicates.toArray(new Predicate[predicates.size()]));
			}
			
			TypedQuery<Clazz> query = em.createQuery(criteriaQuery);
			return query;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new AppSystemException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void addStudentToClass(String studentId, String classId) {
		EntityManager em = null;
		EntityTransaction transaction = null;
		try {
			em = EMF.get().createEntityManager();
			transaction = em.getTransaction();
			transaction.begin();
			
			String jpaql = "SELECT s FROM Student s WHERE s.studentId = :studentId";
			Query query = em.createQuery(jpaql);
			query.setParameter("studentId", studentId);
			List<Student> students = (List<Student>) query.getResultList();
			if (students == null || students.isEmpty()) {
				transaction.commit();
				throw new ObjectNotFoundException(new Exception("Student cannot be found"), Student.class);
			}
			Student student = students.get(0);
			
			jpaql = "SELECT c FROM Clazz c WHERE c.classId = :classId";
			query = em.createQuery(jpaql);
			query.setParameter("classId", classId);
			List<Clazz> classes = (List<Clazz>) query.getResultList();
			if (classes == null || classes.isEmpty()) {
				transaction.commit();
				throw new ObjectNotFoundException(new Exception("Class cannot be found"), Clazz.class);
			}
			Clazz clazz = classes.get(0);
			
			clazz.getStudentKeys().add(student.getStudentKey());
			student.getClassKeys().add(clazz.getClassKey());
			
			transaction.commit();
		} catch (Exception ex) {
			if (transaction != null && transaction.isActive()) {
				transaction.rollback();
			}
			ex.printStackTrace();
			throw new AppSystemException(ex);
		} finally {
			em.close();
		}
	}

	@Override
	public void deleteStudentFromClass(Key studentKey, Key classKey) {
		EntityManager em = null;
		EntityTransaction transaction = null;
		try {
			em = EMF.get().createEntityManager();
			transaction = em.getTransaction();
			transaction.begin();
			
			Student student = em.find(Student.class, studentKey);
			if (student == null) {
				transaction.commit();
				throw new ObjectNotFoundException(new Exception("Student cannot be found"), Student.class);
			}
			
			Clazz clazz = em.find(Clazz.class, classKey);
			if (clazz == null) {
				transaction.commit();
				throw new ObjectNotFoundException(new Exception("Class cannot be found"), Clazz.class);
			}
			
			student.getClassKeys().remove(clazz.getClassKey());
			clazz.getStudentKeys().remove(student.getStudentKey());
			
			transaction.commit();
		} catch (Exception ex) {
			if (transaction != null && transaction.isActive()) {
				transaction.rollback();
			}
			ex.printStackTrace();
			throw new AppSystemException(ex);
		} finally {
			em.close();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Clazz> searchClasses(SearchClassValueObject searchObj) {
		Query query = getSearchQuery(searchObj);
		if (query == null) {
			return new ArrayList<Clazz>();
		} else {
			return (List<Clazz>) query.getResultList();
		}
	}
}
